from __future__ import division
'''
Created on Sep 2, 2009

@author: chaffra
'''

from matplotlib import  __version__
from matplotlib.backend_bases import FigureManagerBase, FigureCanvasBase
from matplotlib.figure import Figure

import re
from xml.sax.saxutils import escape
import xml.dom.minidom


backend_version = __version__

def xmlel(objname):
    if objname.startswith('<'):
        objname = objname[1:-1]
    #objname = objname.replace(' ','_')
    #objname = objname.replace('\'','')
    
    invalid_char = re.compile('\W')
    if invalid_char.search(objname):
        return None
    
    digit = re.compile('[0-9]')
    if digit.match(objname) is not None:
        objname = 'x'+objname
    return objname

def get_xmltype(obj):
    
    instance = re.compile("instance")
    _type = str(type(obj))
    
    if instance.search(_type) is not None:
        _type = str(obj.__class__)
    #print "_type: ",_type
    
    pat = re.compile("'[^']*'")
    xmltype = pat.search(_type)
    if xmltype is None:
            xmltype = _type
    else:    
            xmltype = xmltype.group()
            xmltype = xmltype[1:-1]

    return escape(xmltype)
    
def get_xmlid(obj):
    return str(id(obj))

class Object2Xml:
    
    def __init__(self,ignoreclass=[],ignoreattr=[],ignoretype=re.compile('')):
        
        self._adapt={
                    dict: self._get_xml_dict,
                    list: self._get_xml_list,
                    tuple: self._get_xml_tuple,
                    str: self._get_xml_str,
                    
                }
        
        self._doc = xml.dom.minidom.Document()
    
        self._ignoreclass = ignoreclass
        self._ignoreattr = ignoreattr
        self._ignoretype = ignoretype

        self._vars = []
        
        #print self._xmlstr
        
    def _test_ignore(self,obj,objname):
        _xmltype = get_xmltype(obj)
        nametest = objname is ''
        classtest = isinstance(obj,tuple(self._ignoreclass))    
        typetest = self._ignoretype.search(_xmltype) is not None
        return nametest or classtest or typetest

        
    def get_xml(self, obj, objname):
        """get_xml(obj, objname=None)
        returns an object as XML where Python object names are the tags.
        >>> u={'UserID':10,'Name':'Mark','Group':['Admin','Webmaster']}
        >>> get_xml(u,'User')
        '<User><UserID>10</UserID><Name>Mark</Name><Group>Admin</Group><Group>Webmaster</Group></User>'
        """
        
        _xmltype = get_xmltype(obj)
        _xmlid = get_xmlid(obj)

        objname = xmlel(str(objname))
        
        if self._test_ignore(obj, objname):
            return None
            
#        if objname is '': return None
#        
#        if self.ignoretype.search(_xmltype) is not None: return None
#        
#        elif obj.__class__ in self._ignoreclass:
#            #print obj.__class__
#            return None
        
        if id(obj) in self._vars:
            return self._create_element(objname, _xmlid,None, None)
        else:
            self._vars.append(id(obj))
        
        if getattr(obj,'__class__',None) is None:
            return self._create_element(objname,_xmlid,_xmltype,'')
        
            
        if self._adapt.has_key(obj.__class__):
            return self._adapt[obj.__class__](obj, objname)
        
        elif getattr(obj,'__dict__',None) is None: #scalars arrays etc...
            
            if obj is None:
                o = None
                #print _xmltype,objname
            elif getattr(obj,'__repr__',None) is None:
                o = str(obj)
            else:
                o = obj.__repr__()
                pat = re.compile('\s')
                o = pat.sub('',o)
            return self._create_element(objname, _xmlid, _xmltype, o)
        
        else:
            return self._get_xml_object(obj,objname)
        
    def _create_element(self,name,id,typ=None,content=None):
        element = self._doc.createElement(name)
        if content is not None: 
            content = self._doc.createTextNode(content)
            element.appendChild(content)
        element.setAttribute('id',id)
        if typ is not None:
            element.setAttribute('type',typ)
        return element
        
    def _get_xml_str(self,s,objname=None):
        if s == '':
            s = "''"
        return self._create_element(objname,get_xmlid(s),get_xmltype(s),s)

    def _get_xml_dict(self,indict, objname=None):
        element = self._create_element(objname,get_xmlid(indict),'dict',None)   
        for k, v in indict.items():
            #print "attribute: ",k
            k = xmlel(str(k))
            if k is None:
                continue
            else:
                child = self.get_xml(v, k)
                if child is not None:
                    element.appendChild(child)
        return element
    
    def _get_xml_tuple(self,intuple,objname=None):
        return self._get_xml_sequence(intuple, objname,'tuple')
    
    def _get_xml_list(self,inlist,objname=None):
        return self._get_xml_sequence(inlist, objname,'list')

    def _get_xml_sequence(self,seq, objname,typ):
        element = self._create_element(objname,get_xmlid(seq),typ,None)
        for i in seq:
            child = self.get_xml(i, objname)
            if child is not None:
                element.appendChild(child)
        return element
        

    def _get_xml_object(self,obj, objname):
        
        element = self._create_element(objname,get_xmlid(obj),get_xmltype(obj),None)
        #if id(obj) not in self._refs:
        #    self._refs.append(id(obj))
        for k in obj.__dict__:
            #print k
            if type(k) != type(''):
                continue
            if k.startswith('__') and k.endswith('__'):
                continue
            
            k = xmlel(k)
            if k is None:
                continue
                
            if objname+ '.' + k in self._ignoreattr:
                #print objname+ '.' + k
                continue
                
            try:
                v = getattr(obj,k)
                vk = getattr(v,k,None)
                if vk is not None and id(v) != id(vk) and v.__class__ is vk.__class__:  #attempt to  avoid recursion limit error.
                    #print 'recursion of',type(obj),'::',type(v),'::',type(vk),':::',k
                    v = escape(v.__repr__())
            except Exception, e:
                #print type(obj)
                #print k
                #print str(e)
                continue
            child = self.get_xml(v, k)
            if child is not None:
                element.appendChild(child)
        return element


class Figure2XML(Object2Xml,object):
    
    def __init__(self):
        
        ignoreclass = [FigureCanvasBase, None.__class__]
        
        objname = 'figure'

        ignoreattr = [
                            "%s.figurePatch" % objname, "%s.bbox" % objname, "%s.bbox_inches" % objname,
                            "%s.patch" % objname, "%s._axstack" % objname, "%s.callbacks" % objname,
                            "%s.artists" % objname, "%s._cachedRenderer" % objname,
                            'axes.axesPatch','axes.patch','axes.spines', #'axes._position',
                            'axes._originalPosition', 'axes._get_lines', 'axes._get_patches_for_fill',
                            'axes.callbacks','axes.figbox','axes.dataLim',
                            'lines._markers','lines.markers','lines.filled_markers','lines.lineStyles',
                            'lines.validCap','lines.validJoin','lines._xorig','lines._yorig',
                            'lines._x','lines._y','_xy.T', '_xy.base', '_xy.imag','_xy.real','_xy._data',
                            'xaxis.majorTicks', 'xaxis.minorTicks','xaxis.callbacks','xaxis.converter',
                            #'xaxis.major','xaxis.minor',
                            'yaxis.majorTicks', 'yaxis.minorTicks','yaxis.callbacks','yaxis.converter',
                            #'yaxis.major','yaxis.minor',
                            
                            ]
        
        ignoretype = re.compile("("+
                                     "function|instancemethod|module|method|type|"+
                                     "matplotlib.cbook|matplotlib.path|"+
                                     "matplotlib.backends|"+
                                     "matplotlib.transforms.BlendedGenericTransform|"+
                                     "matplotlib.transforms.BboxTransformFrom|"+
                                     "matplotlib.transforms.CompositeGenericTransform|"+
                                     "matplotlib.transforms.BlendedAffine2D|"+
                                     "matplotlib.transforms.TransformWrapper|"+
                                     "matplotlib.transforms.TransformedPath|"+
                                     "matplotlib.transforms.BboxTransformTo|"+
                                     "matplotlib.transforms.TransformedBbox|"+
                                     "matplotlib.transforms.Affine2D|"+
                                     "matplotlib.transforms.IdentityTransform|"+
                                     "matplotlib.transforms.ScaledTranslation|"+
                                     "matplotlib.transforms.CompositeAffine2D"+
                                     ")")
        
        Object2Xml.__init__(self,ignoreclass,ignoreattr,ignoretype)


########################################################################
#
# The following functions and classes are for pylab and implement
# window/figure managers, etc...
#
########################################################################


def new_figure_manager(num, *args, **kwargs):
    """
    Create a new figure manager instance
    """
    # if a main-level app must be created, this is the usual place to
    # do it -- see backend_wx, backend_wxagg and backend_tkagg for
    # examples.  Not all GUIs require explicit instantiation of a
    # main-level app (egg backend_gtk, backend_gtkagg) for pylab
    FigureClass = kwargs.pop('FigureClass', Figure)
    thisFig = FigureClass(*args, **kwargs)
    canvas = FigureCanvasXml(thisFig)
    manager = FigureManagerXml(canvas, num)
    return manager

class FigureCanvasXml(FigureCanvasBase):
    """
    The canvas the figure renders into.  Calls the draw and print fig
    methods, creates the renderers, etc...

    Public attribute

      figure - A Figure instance
    """

    def draw(self):
        pass

    filetypes = {'xml': 'Matplotlib XML Format'}

    def get_default_filetype(self):
        return 'xml'

    def print_xml(self, filename, **kwargs):
        
        if filename is None:
            return
        
        fig2xml = Figure2XML()
        xmlobj = fig2xml.get_xml(self.figure, 'figure')
        if xmlobj is not None:
            f = open(filename,'w')
            f.write(xmlobj.toxml())
            f.close()

class FigureManagerXml(FigureManagerBase):
    pass

FigureManager = FigureManagerXml
